#ifndef __LIOIMG_H_
#define __LIOIMG_H_

#include "liomath.h"

#define XMD_H     // fixes a type redefiniton issue
#ifdef  __cplusplus
	extern "C" {
#endif
#include "../extern/jpeglib/jpeglib.h"
#ifdef  __cplusplus
	}
#endif

#define INT_BLACK		0xFF000000
#define INT_WHITE		0xFFFFFFFF
#define INT_GRAY		0xFFAAAAAA
#define INT_WGRAY		0xFFC0C0C0
#define INT_DGRAY		0xFF303030
#define INT_RED			0xFF0000FF
#define INT_WRED		0xFFAAAAFF
#define INT_DRED		0xFF0000AA
#define INT_GREEN		0xFF00FF00
#define INT_WGREEN		0xFFAAFFAA
#define INT_DGREEN		0xFF00AA00
#define INT_BLUE		0xFFFF0000
#define INT_WBLUE		0xFFFFAAAA
#define INT_DBLUE		0xFFAA0000
#define INT_CYAN		0xFFFFFF00
#define INT_WCYAN		0xFFFFFFAA
#define INT_DCYAN		0xFFAAAA00
#define INT_MAGENTA		0xFFFF00FF
#define INT_WMAGENTA	0xFFFFAAFF
#define INT_DMAGENTA	0xFFAA00AA
#define INT_YELLOW		0xFF00FFFF
#define INT_WYELLOW		0xFFAAFFFF
#define INT_DYELLOW		0xFF00AAAA
#define INT_ORANGE		0xFF00C0FF

#define PIXEL_COMP_OLD	0xFF
#define PIXEL_COMP_GRAY	0x01
#define PIXEL_COMP_RGB	0x03
#define PIXEL_COMP_RGBA	0x04

#define INT_TO_BYTE(color) ((BYTE*)(&(color)))
#define BYTE_TO_FLOAT(comp) ((float)((comp)*0.003921568627450980392156862745098))
#define FLOAT_TO_BYTE(f) ((BYTE)((round(f*0xff)&0xff)))

#define HUE 0
#define SAT 1
#define VAL 2
#define LUM 2

namespace Lio {

enum RGB_COMPS
{
	RED,
	GREEN,
	BLUE,
	ALPHA
};
enum COLOR_TRANS
{
	NO_TRANS,
	RGB_TO_XYZ,
	XYZ_TO_RGB,
	RGB_TO_CMY,
	CMY_TO_RGB,
	RGB_INVERT,
	RGB_NEGATIVE,
	RGB_TO_HSV,
	HSV_TO_RGB,
	RGB_TO_HSL,
	HSL_TO_RGB,
	RGB_TO_GRAY
};
enum COLOR_SPACES
{
	RGB_REAL_D65,
	RGB_CCIR_601,
	RGB_Adobe_D65,
	RGB_Apple_D65,
	RGB_Best_D50,
	RGB_Beta_D50,
	RGB_Bruce_D65,
	RGB_CIE_E,
	RGB_ColorMatch_D50,
	RGB_DonRGB4_D50,
	RGB_ECI_D50,
	RGB_EktaSpacePS5_D50,
	RGB_NTSC_C,
	RGB_PAL_SECAM_D65,
	RGB_ProPhoto_D50,
	RGB_SMPTE_C_D65,
	RGB_sRGB_D65,
	RGB_WideGamut_D50,
	RGB_count
};

/// the tga header (18 bytes!)
#pragma pack(push,1) // turn off data boundary alignment
struct TGAHeader
{
	// sometimes the tga file has a field with some custom info in. This 
	// just identifies the size of that field. If it is anything other
	// than zero, forget it.
	unsigned char m_iIdentificationFieldSize;
	// This field specifies if a colour map is present, 0-no, 1 yes...
	unsigned char m_iColourMapType;
	// only going to support RGB/RGBA/8bit - 2, colour mapped - 1
	unsigned char m_iImageTypeCode;
	// ignore this field....0
	unsigned short m_iColorMapOrigin;
	// size of the colour map
	unsigned short m_iColorMapLength;
	// bits per pixel of the colour map entries...
	unsigned char m_iColourMapEntrySize;
	// ignore this field..... 0
	unsigned short m_iX_Origin;
	// ignore this field..... 0
	unsigned short m_iY_Origin;
	// the image width....
	unsigned short m_iWidth;
	// the image height.... 
	unsigned short m_iHeight;
	// the bits per pixel of the image, 8,16,24 or 32
	unsigned char m_iBPP;
	// ignore this field.... 0
	unsigned char m_ImageDescriptorByte;
};
struct RGBComp
{
	BYTE red;
	BYTE green;
	BYTE blue;
};
struct RGBAComp
{
	BYTE red;
	BYTE green;
	BYTE blue;
	BYTE alpha;
};
#pragma pack(pop)

extern const double	COLOR_TRANS_TABLE[][2][3][3];

// COLOR FORMAT CONVERTIONS
inline DWORD colorFloatToIntA(float red, float green, float blue, float alpha)
{
	return	FLOAT_TO_BYTE(alpha)<<24|
			FLOAT_TO_BYTE(blue)<<16|
			FLOAT_TO_BYTE(green)<<8|
			FLOAT_TO_BYTE(red);
}
inline DWORD colorFloatToIntA(const float *color)
{
	return	FLOAT_TO_BYTE(color[ALPHA])<<24|
			FLOAT_TO_BYTE(color[BLUE])<<16|
			FLOAT_TO_BYTE(color[GREEN])<<8|
			FLOAT_TO_BYTE(color[RED]);
}
inline DWORD colorFloatToInt(float red, float green, float blue)
{
	return	INT_BLACK|
			FLOAT_TO_BYTE(blue)<<16|
			FLOAT_TO_BYTE(green)<<8|
			FLOAT_TO_BYTE(red);
}
inline DWORD colorFloatToInt(const float *color)
{
	return	INT_BLACK|
			FLOAT_TO_BYTE(color[BLUE])<<16|
			FLOAT_TO_BYTE(color[GREEN])<<8|
			FLOAT_TO_BYTE(color[RED]);
}
inline DWORD colorByteToIntA(BYTE red, BYTE green, BYTE blue, BYTE alpha)
{
	return	(alpha<<24)|(blue<<16)|(green<<8)|red;
}
inline DWORD colorByteToIntA(const BYTE *color)
{
	return	(color[ALPHA]<<24)|(color[BLUE]<<16)|(color[GREEN]<<8)|color[RED];
}
inline DWORD colorByteToInt(BYTE red, BYTE green, BYTE blue)
{
	return	INT_BLACK|(blue<<16)|(green<<8)|red;
}
inline DWORD colorByteToInt(const BYTE *color)
{
	return	INT_BLACK|(color[BLUE]<<16)|(color[GREEN]<<8)|color[RED];
}
inline void colorByteToFloatA(const BYTE *in, float *out)
{
	out[ALPHA]=BYTE_TO_FLOAT(in[ALPHA]);
	out[BLUE]=BYTE_TO_FLOAT(in[BLUE]);
	out[GREEN]=BYTE_TO_FLOAT(in[GREEN]);
	out[RED]=BYTE_TO_FLOAT(in[RED]);
}
inline void colorByteToFloat(const BYTE *in, float *out)
{
	out[BLUE]=BYTE_TO_FLOAT(in[BLUE]);
	out[GREEN]=BYTE_TO_FLOAT(in[GREEN]);
	out[RED]=BYTE_TO_FLOAT(in[RED]);
}
inline void colorFloatToByteA(const float *in, BYTE *out)
{
	out[ALPHA]=FLOAT_TO_BYTE(in[ALPHA]);
	out[BLUE]=FLOAT_TO_BYTE(in[BLUE]);
	out[GREEN]=FLOAT_TO_BYTE(in[GREEN]);
	out[RED]=FLOAT_TO_BYTE(in[RED]);
}
inline void colorFloatToByte(const float *in, BYTE *out)
{
	out[BLUE]=FLOAT_TO_BYTE(in[BLUE]);
	out[GREEN]=FLOAT_TO_BYTE(in[GREEN]);
	out[RED]=FLOAT_TO_BYTE(in[RED]);
}
// COLOR OPERATIONS
DWORD colorMix(DWORD color1, DWORD color2, double ratio = 0.5);
float colorFloatToGray(const float *color, int table = 0);

BOOL colorRGBtoHSL(const float *rgbf, float *hsl);
BOOL colorHSLtoRGB(const float *hsl, float *rgbf);
inline DWORD colorHueToRGB(float hue)
{
	float rgb[3],hsl[3]={hue,1.0f,0.5f};
	colorHSLtoRGB(hsl,rgb);
	return colorFloatToInt(rgb);
}
inline void colorHueToRGB(float hue, float *rgb)
{
	float hsl[3]={hue,1.0f,0.5f};
	colorHSLtoRGB(hsl,rgb);
}
inline BOOL colorCmp(const float *rgb1, const float *rgb2, float toler = 0)
{
	return
			!(rgb1[RED]<rgb2[RED]*(1.0f-toler) || rgb1[RED]>rgb2[RED]*(1.0f+toler))
		&&	!(rgb1[GREEN]<rgb2[GREEN]*(1.0f-toler) || rgb1[GREEN]>rgb2[GREEN]*(1.0f+toler))
		&&	!(rgb1[BLUE]<rgb2[BLUE]*(1.0f-toler) || rgb1[BLUE]>rgb2[BLUE]*(1.0f+toler));
}
inline BOOL colorCmp(const BYTE *rgb1, const BYTE *rgb2, float toler = 0)
{
	return
			!(rgb1[RED]<rgb2[RED]*(1.0f-toler) || rgb1[RED]>rgb2[RED]*(1.0f+toler))
		&&	!(rgb1[GREEN]<rgb2[GREEN]*(1.0f-toler) || rgb1[GREEN]>rgb2[GREEN]*(1.0f+toler))
		&&	!(rgb1[BLUE]<rgb2[BLUE]*(1.0f-toler) || rgb1[BLUE]>rgb2[BLUE]*(1.0f+toler));
}
inline DWORD colorMax(const float *rgbf)
{
	if(rgbf[BLUE]>rgbf[GREEN])
	{
		if(rgbf[BLUE]>rgbf[RED])
			return BLUE;
	}
	else
	{
		if(rgbf[GREEN]>rgbf[RED])
			return GREEN;
	}
	return RED;
}
inline DWORD colorMin(const float *rgbf)
{
	if(rgbf[BLUE]<rgbf[GREEN])
	{
		if(rgbf[BLUE]<rgbf[RED])
			return BLUE;
	}
	else
	{
		if(rgbf[GREEN]<rgbf[RED])
			return GREEN;
	}
	return RED;
}
void colorGamma(float *rgbf, double gamma, float *out = NULL);
void colorSat(float *rgbf, double kr, double kg, double kb, float *out = NULL);
inline void colorSat(float *rgbf, double k, float *out = NULL)
{
	colorSat(rgbf,k,k,k,out);
}
double colorMaxSat(float ** rgbData, int count = 1);
inline double colorMaxSat(float * rgb)
{
	return colorMaxSat(&rgb,1);
}
BOOL colorTrans(float **rgbData, float **outData = NULL, int count = 1, int type = 0, int table = 0);
inline BOOL colorTrans(float *rgb, float *out = NULL, int type = 0, int table = 0)
{
	if(!out)
		out=rgb;
	return colorTrans(&rgb,&out,1,type,table);
}

BOOL colorHisto(float **rgbData, int count, int *histo, int channel, int levels = 256, BOOL clear = TRUE);
inline BOOL colorHisto(float **rgbData, int count, int *histo, int levels = 256, BOOL clear = TRUE)
{
	if(!colorHisto(rgbData,count,histo,RED,levels,clear))
		return FALSE;
	if(!colorHisto(rgbData,count,histo,GREEN,levels,FALSE))
		return FALSE;
	if(!colorHisto(rgbData,count,histo,BLUE,levels,FALSE))
		return FALSE;
	return TRUE;
}
inline DWORD colorInv(int rgb){return 0xff000000|~rgb;}
inline DWORD colorRand(){return colorHueToRGB((float)random());}

class Image
{
protected:
	Vector<BYTE> buffer;
	static BOOL checkPixelComp(BYTE pixelComp);
public:
	int	width,height;
	BYTE *data,comps;
	BOOL setSize(int newWidth, int newHeight, BYTE pixelComp = PIXEL_COMP_OLD, BOOL clear = TRUE);
	BOOL bind(BYTE * newData, int newWidth, int newHeight, int memType = 0, BYTE pixelComp = PIXEL_COMP_OLD);
	void destroy();
	BOOL replace(const BYTE * newData);
	BOOL copy(const Image &image);
	int getSize() const {return buffer.size;}
	int length() const {return width*height;}
	void clear();
	void setPixel(DWORD color, int x, int y);
	int getPixel(int x, int y)const;
	void flipV();
	void flipH();
	void flipC();
	void negative();
	Image():data(NULL),width(0),height(0),comps(PIXEL_COMP_RGB){}
	Image(const Image &image){copy(image);}
	Image& operator =(const Image& image){copy(image);return *this;}
};

BOOL writeBitmap(FILE *fp, Image *image);
BOOL readBitmap(FILE *fp, Image *image);
BOOL readBitmap(BITMAPINFO *info, BYTE *data, DWORD	datasize, Image *image);
BOOL writeTGA(FILE *fp, Image *image);
BOOL readTGA(FILE *fp, Image *image);
BOOL writeJPEG(FILE * file, Image *image, int quality = 100);
BOOL readJPEG(FILE *  file, Image *image);

inline BOOL writeBitmap(CSTR fileName, Image *image)
{
	File file;
	return (file.open(fileName, _T("wb")))?writeBitmap(file,image):FALSE;
}
inline BOOL readBitmap(CSTR fileName, Image *image)
{
	File file;
	return (file.open(fileName, _T("rb")))?readBitmap(file,image):FALSE;
}
inline BOOL writeTGA(CSTR fileName, Image *image)
{
	File file;
	return (file.open(fileName, _T("wb")))?writeTGA(file,image):FALSE;
}
inline BOOL readTGA(CSTR fileName, Image *image)
{
	File file;
	return (file.open(fileName, _T("rb")))?readTGA(file,image):FALSE;
}
inline BOOL writeJPEG(CSTR fileName, Image *image, int quality = 100)
{
	File file;
	return (file.open(fileName, _T("wb")))?writeJPEG(file,image,quality):FALSE;
}
inline BOOL readJPEG(CSTR fileName,Image *image)
{
	File file;
	return (file.open(fileName, _T("rb")))?readJPEG(file,image):FALSE;
}

}; //namespace Lio

#endif // __LIOIMG_H_